feat(managed-agents): close five Claude Code agent-config gaps - #4557
Conversation
952e5c4 to
c1397bf
Compare
Add the Auto variant to PermissionMode (wire string 'auto'; #4557 adds the same variant from the claude-config arc — this commit establishes the contradiction logic ahead of that merge so the rebase is mechanical). Auto mode = fully autonomous execution; model-gated (requires supportsAutoMode); the adapter self-approves all tool calls internally and never emits session/request_permission. Mode matrix: - allow + auto → compatible (transmit as-is; both want unattended approval) - ask + auto → startup error (card never fires — ask becomes a dead letter) - reject + auto → startup error (inverted-security worst case: policy says deny while adapter silently auto-approves everything) Tests: 4 new pinned tests (allow+auto ok, ask+auto error, reject+auto error, wire string correct). Total: 724 passing. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
kalvinnchau
left a comment
There was a problem hiding this comment.
[P2] Include the request nonce in invalid_value acknowledgements — crates/buzz-acp/src/lib.rs:1320
The validation-rejection branch returns before the common acknowledgement builder adds nonce. Desktop always supplies a nonce and awaitEffortOutcome rejects every acknowledgement whose nonce does not match, so a genuinely invalid selection is ignored until the 8-second timeout and then presented as “Applies at next session,” even though the pool was deliberately left unchanged. Add the incoming nonce to this early acknowledgement (and cover the real nonce-bearing invalid-value path rather than only nonce-free fixtures).
[P2] Seed committed_effort when the startup effort is successfully applied — crates/buzz-acp/src/pool.rs:791
A persisted startup effort is applied by create_session_and_apply_model, but its checkout has desired_effort_gen == None; return_agent copies it into desired_effort and never records it in committed_effort. If the user subsequently selects another valid effort and the adapter rejects/times out, the failure path rolls desired_effort back to committed_effort, which is still None, instead of restoring the previously confirmed startup value. Record a successful startup application as the committed baseline (or initialize the pool baseline from the persisted value once capabilities confirm it), and add a startup-success → live-pick-failure regression.
[P2] Serialize generation resolution across parallel workers — crates/buzz-acp/src/pool.rs:805
Every worker checked out for the same generation independently emits a terminal acknowledgement and independently commits or rolls back the shared pool state. With parallelism >1, if worker B fails and returns first it rolls desired_effort back; if worker A then returns success, it updates only committed_effort and leaves desired_effort rolled back. The observer can persist A's ok, while future checkouts receive the old/default value. The reverse return order produces a different result, so pool state is scheduler-dependent. Resolve each generation once with explicit aggregate semantics (and emit one terminal outcome), or ensure a later same-generation success restores desired_effort consistently; cover both return orders with two workers.
Validation at exact PR HEAD b7c99fb94808cf3b07bb7540121d8eb4513578dd: cargo test -p buzz-acp --lib passed (723 tests); node --test src/features/agents/lib/effortOutcome.test.mjs passed (15 tests). These paths are not covered by the current suite.
49177be to
756ed48
Compare
a4e36e2 to
bcff68d
Compare
c3acf91 to
b337a4c
Compare
kalvinnchau
left a comment
There was a problem hiding this comment.
[P2] Do not treat a switch that never landed as success — desktop/src/features/agents/lib/liveSwitchOutcome.ts:116
Every status other than unsupported_model, failure, and sent falls through to the positive-terminal counter. That includes turn_ending and no_active_turn, but the producer explicitly emits those when it could not deliver the switch: turn_ending means the control oneshot was already consumed, and no_active_turn means neither an in-flight task nor an idle session-owning agent could be found (crates/buzz-acp/src/lib.rs:1355-1377). This race is reachable when activeTurns is stale between the picker snapshot and harness receipt, or when another control signal is already ending the turn. The picker then shows “Model switched for this session” even though desired_model was never set and no later apply will occur; the added test at liveSwitchOutcome.test.mjs:87-100 currently codifies that false success for turn_ending.
Handle statuses exhaustively. Count only switched as an applied terminal; keep sent provisional, and map turn_ending / no_active_turn to an honest non-success outcome (or retry/persist through a path that actually applies the requested model). Add regression rows for both producer statuses.
kalvinnchau
left a comment
There was a problem hiding this comment.
Re-reviewed exact head e7049b4b6f7c9404205db7a0cb86677aaeaf477c against merge base 439c03749182495ee09f85a73423dd17e7ccda61.
The prior P2 is resolved. liveSwitchOutcome.ts now treats only switched as applied success, leaves sent provisional, maps turn_ending and no_active_turn to not_delivered, and ignores unknown statuses. Regression coverage exercises both non-delivery statuses and future unknown statuses. The subsequent huddle import consolidation is semantics-preserving.
No additional findings. Validation on the exact head: cargo test -p buzz-acp passed (816 library + 9 lifecycle tests); desktop pnpm test passed (5,013 tests); git diff --check clean. GitHub CI remained in progress at review time.
…Auto mode Three Claude Code agent-config gaps that share the spawn-time env surface: - #3493: honor a user-set CLAUDE_CONFIG_DIR when reading claude settings.json and .claude.json. The claude 2.1.x binary resolves both files relative to CLAUDE_CONFIG_DIR (falling back to homedir), so the config panel must read from the same directory the agent actually uses. Surfaces a Keychain caveat note when a custom dir is active, since a custom config dir maps to a fresh Keychain namespace unless CLAUDE_SECURESTORAGE_CONFIG_DIR is also set. - #2692: ANTHROPIC_MODEL is the single startup model authority for claude. Local spawns write ANTHROPIC_MODEL and strip BUZZ_ACP_MODEL so the harness never sees two model authorities; remote deploys send ANTHROPIC_MODEL in policy_env instead of BUZZ_ACP_MODEL. All non-claude runtimes are unchanged and continue to use BUZZ_ACP_MODEL. - #2884: add PermissionMode::Auto (wire string "auto"). Model-gated and degrades to default when the active model does not support it. Closes #2692 Closes #2884 Closes #3493 Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Effort now flows from the running session's discovered `thought_level` config option through the config surface to a local-only write control and a read-only two-facts display, closing the effort gap in the Claude agent-config set. - Reader discovers the `thought_level` config option from the session cache (never hardcoded) and populates `effort_config_id` / `effort_options` on RuntimeConfigSurface. The canonical effort tier orders record env > record.effort_level (BuzzExplicit) > ACP > persona > global > definition > file, so the panel shows the effort the next spawn launches with while `resolve_with_override` exposes the running ACP value as the struck-through override — neither masks the other silently. - `persist_agent_effort_level` is a direct-write Tauri setter (writes record.effort_level + updated_at, save_managed_agents) that rejects non-local backends: remote effort is set at deploy time via policy_env, not here. - EffortPickerField is the local-only write control, mounted in AgentInstanceEditDialog beside the model block, gated on a local backend AND a discovered effortConfigId. It persists directly and invalidates the config surface, mirroring the setManagedAgentAutoRestart standalone-setter precedent so the frozen UpdateManagedAgentInput shape stays frozen. - buzz-acp applies the startup effort env at session start. Contract notes for review: - Picker/display split: the WRITE control lives in AgentInstanceEditDialog; the read-only configured-vs-current two-facts DISPLAY stays in AgentConfigPanel's thinkingEffort normalized field (the reader work feeds it). - RelayMeshConfig extracted from types.rs into types/relay_mesh.rs to keep the grandfathered file under its size ceiling; no behavior change. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
A live model switch collapsed adapter rejection into success: apply_model_switch
returned Ok on both an accepted switch and an application-level refusal, so the
caller cached pre-switch capabilities as if they described the target model and
Desktop reported the pick as landed.
Introduce ModelSwitchOutcome::{Applied(Value),Rejected}. Transport-class errors
still propagate as Err (respawn the poisoned stdio); an application-level refusal
is now Rejected. The caller drives everything off post_switch_snapshot: Applied
refreshes model_capabilities from the target model's echoed configOptions (or
drops to None when none are echoed, so a pre-switch snapshot is never mistaken
for the target's); Rejected preserves pre-switch caps and emits a failure
control_result. Effort resolution and the session-config capture read the
post-switch snapshot so they converge on the model the session actually runs,
and modelOverridden is gated on switch_succeeded.
Switch results carry request and channel identity so a reconnect replay or a
stale result from an earlier pick can no longer settle the wrong operation, and
dispatch dedup fires side-effect listeners only for accepted (non-duplicate)
events. The busy-path switch no longer infers success from timeout silence: the
deferred apply emits a correlated positive "switched" control_result when it
lands, liveSwitchOutcome.ts resolves "ok" only from that real terminal, and the
fallback timeout resolves an honest "pending" (accepted, apply deferred) rather
than a false "ok". ModelPicker surfaces a distinct toast per outcome — failed,
unsupported, and pending.
Co-authored-by: Will Pfleger <pfleger.will@gmail.com>
Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
The live-switch outcome counted every status other than unsupported_model/ failure/sent as a positive terminal, so turn_ending and no_active_turn — which the harness emits when the switch was NEVER delivered (control oneshot already consumed; no in-flight task and no idle session) — resolved "ok" and toasted "Model switched for this session." In both the desired model is never set and nothing applies later. Handle statuses exhaustively: only switched confirms an applied positive terminal, sent stays provisional, turn_ending/no_active_turn fail-fast to a new not_delivered outcome with a truthful toast, and any unknown future status is inert rather than default-counted as success. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Fold the three separate huddle:: use statements (audio_output, reconnect, root) into one nested use, removing a line so lib.rs stays at the file-size ratchet ceiling after main's own churn lowered the base. Semantics-preserving. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
e7049b4 to
fb4f410
Compare
Main grew spawn_snapshot/tests.rs's neighbors so the PR's identical +143 test delta now lands the file at 1071 lines, tripping the 1000-line desktop file-size ratchet. Relocate the self-contained B5 single-canonical effort block (and its two local helpers) into the sibling tests_ext.rs already carved out for this purpose. Pure code motion, zero behavior change; both files now sit below the ceiling. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Main (block#4557) reworked `processLiveObserverEvents` so the side-effect dispatch loop iterates only the ACCEPTED events. The observer relay replays five minutes of frames on reconnect, and re-dispatching one would let a replayed `control_result` re-settle a live model switch. It also renamed `addedEvents` to `accepted`. This branch had added circuit-breaker tracking to the same function, and it iterates the FULL envelope on purpose: `appendAgentEvents`' per-agent eviction floor and `applyCircuitEvent`'s per-slot ordering gate compare against different reference points, so a frame the floor rejects can still be the first one ever seen for its circuit slot. Taking either side wholesale destroys the other's guarantee silently -- keeping main's loop drops legitimate circuit transitions; keeping this branch's loop reinstates double-dispatch on replay. So the two passes are separated: - circuit state consumes every event in the envelope. Replay is handled by applyCircuitEvent's own per-slot gate, not by the accepted filter. - the side-effect listeners consume only `accepted`, unchanged from main. - publication keeps main's targeted payload, plus this branch's circuit-only wake for the case where nothing was retained but the badge must repaint. The conflict is not a side effect of the previous merge commit: `git merge-tree origin/main 64b8dcd` conflicts too, so the original branch tip had already been broken by block#4557 landing. Verified in this worktree on x86_64-pc-windows-msvc: - `pnpm exec tsc --noEmit`: clean. - `pnpm check` (biome + file-size ratchet + px-text + pubkey guards): exit 0. - `pnpm test`: 5045 passed, 0 failed. Signed-off-by: Michael Feth <michael@jira-flow.com>
#4557 mounted EffortPickerField into AgentInstanceEditDialog, which this PR deletes. Fold the write control into the merged dialog's instance section under the same gating (local backend + discovered effortConfigId), wiring the agent config surface query in the parent. Preserves the direct-write path, surface invalidation, and edit-agent-effort id unchanged. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
block#4557 ("close five Claude Code agent-config gaps") landed on main and conflicts with this branch. Resolutions: 1. `lib.rs` deep_link import. Both sides had independently dropped the two standalone `use huddle::` lines; the only real difference was the glob. The glob stays -- it is what keeps lib.rs under the size ratchet, four other globs in the same file make it idiomatic, and it cannot go stale when main adds another deep_link symbol. 2. `lib.rs` generate_handler list. Additive on both sides: main added `persist_agent_effort_level`, this branch the two routing-policy commands. All three kept. 3. `reader.rs` config_file_path. Main extracted the logic into `config_file_path_for_runtime` and threaded a new `claude_config_dir` parameter through it, while this branch had added an OpenCode special case at the old call site. Taking main's side alone silently drops OpenCode config discovery, so the special case moved INTO main's helper -- where it now sits beside the `opencode` arm that already exists in the sibling `mcp_config_file_path_for_runtime`. Two further defects that no textual merge could surface: - `reader_tests_opencode.rs` did not compile. Main gave `read_config_surface` a fifth parameter (`claude_config_dir`); this file is new on this branch and untouched by main, so git merged it cleanly and the call kept passing four arguments. This is the third time this branch has been broken by exactly that shape -- a signature or struct change upstream in code the branch never touched. - The preset-logo guard was asserting on itself, not on the data. This branch moved `KNOWN_ACP_RUNTIMES` out of `discovery.rs` into `discovery/known_runtimes.rs` and gave it `pub(super)` visibility, but left the guard reading the old path with a regex requiring a bare `const`. Both halves stopped matching, so it failed on `could not locate KNOWN_ACP_RUNTIMES` before checking anything. This was broken on the branch tip before this merge, not caused by it. The guard now reads the real file and tolerates an optional visibility modifier, so a future move cannot re-break the match the same way. Verified in this worktree on x86_64-pc-windows-msvc: - `cargo check --manifest-path desktop/src-tauri/Cargo.toml --all-targets`: clean (was error[E0061]). - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib`: 2450 passed, 1 failed -- `claude_spawn_uses_the_probed_cli_executable`, which passes in isolation and fails the same way on clean main. - `pnpm exec tsc --noEmit`: clean. - `pnpm check` (biome + file-size ratchet + px-text + pubkey guards): exit 0. - `presetLogos.test.mjs`: 11 passed (was a hard failure). Signed-off-by: Michael Feth <michael@jira-flow.com>
…c-agent-commit-identity * origin/main: feat(managed-agents): close five Claude Code agent-config gaps (#4557) chore(hooks): keep mobile analysis out of pre-commit (#6236) fix(shared-ui): delay hover disclosures by default (#5821) Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
#4557 mounted EffortPickerField into AgentInstanceEditDialog, which this PR deletes. Fold the write control into the merged dialog's instance section under the same gating (local backend + discovered effortConfigId), wiring the agent config surface query in the parent. Preserves the direct-write path, surface invalidation, and edit-agent-effort id unchanged. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
* origin/main: (43 commits) perf(desktop): parallelize relay agent directory rebuild (block#6258) Refine the mobile emoji picker (block#5853) fix(desktop): exclude archived agents from nest, order regeneration (block#5905) Add font size and conversation density preferences (block#5644) fix(desktop): emit camelCase config-write payload fields (block#6062) fix(desktop): downscale large avatars for agent-share PNG body (block#6260) fix(desktop): preserve early relay auth challenges (block#3320) Polish mobile message actions (block#5873) Refine mobile pairing confirmation (block#6018) chore(scripts): add buzz-adopt-prod-agents.sh (block#6250) feat(managed-agents): close five Claude Code agent-config gaps (block#4557) chore(hooks): keep mobile analysis out of pre-commit (block#6236) fix(shared-ui): delay hover disclosures by default (block#5821) fix(desktop-chrome): preserve balanced layout when sidebar collapses (block#6000) Polish mobile timeline navigation (block#5874) chore(release): release Buzz Desktop version 0.5.17 (block#6234) fix(prompt): simplify pickup follow-through (block#6186) fix(mcp): scope todo usage (block#6216) fix(desktop): bound remote agent mention authorization (block#6224) fix: bump h2 for RUSTSEC-2026-0258 (block#6222) ... Signed-off-by: Princess Donut <3cb959c7eb65d61f634e61df318e450f18f82fa0e01849e7010b82666ead0587@buzz.block.builderlab.xyz> # Conflicts: # desktop/src/main.tsx # mobile/ios/Podfile.lock
Resolves seven conflicts, all in the desktop managed-agent surface, where upstream's "close five Claude Code agent-config gaps" (block#4557) and "exclude archived agents from nest" (block#5905) landed on code the fork had relocated to stay under the file-size ratchet: - types.rs / types.ts / nest/tests.rs: fork had moved the conflicting blocks to record_views.rs, managedAgent.ts, and (upstream) render_tests.rs. Kept the relocated homes and ported upstream's new `effort_level` field into record_views.rs plus the fork's community_relay_url / residual_deployments / waker_enabled fields into render_tests.rs. - readiness.rs / discovery/tests.rs: fork builds fixtures from JSON so new optional record fields don't churn them; kept that over upstream's struct literals. - mod.rs / tauriManagedAgents.ts: both sides added declarations at the same spot; kept both. The merge also pushed two files past the desktop file-size ratchet through accumulated growth on both sides. Split them along the module's existing seams rather than raising the limit: - commands/agents_deploy.rs (1037 -> 536): tests moved to agents_deploy_tests.rs, matching the agent_config.rs idiom. - managed_agents/runtime.rs (1003 -> 981): git credential-helper env moved to runtime/git_credentials.rs. Verified: workspace clippy + fmt, Tauri clippy/fmt/tests (2650 passed), desktop tsc/biome/tests (5169 passed)/build, desktop file-size + px-text guards, mobile format/analyze/tests (1523 passed) + file-size guard. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Junchao Yan <yjc801@gmail.com>
…-in-thread * origin/main: (32 commits) Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311) fix(desktop): morph the drawer panel icon instead of sliding it (#6306) feat(desktop): refine repository-aware project workspaces (#6003) Fix mobile Activity thread navigation (#5850) perf(desktop): parallelize relay agent directory rebuild (#6258) Refine the mobile emoji picker (#5853) fix(desktop): exclude archived agents from nest, order regeneration (#5905) Add font size and conversation density preferences (#5644) fix(desktop): emit camelCase config-write payload fields (#6062) fix(desktop): downscale large avatars for agent-share PNG body (#6260) fix(desktop): preserve early relay auth challenges (#3320) Polish mobile message actions (#5873) Refine mobile pairing confirmation (#6018) chore(scripts): add buzz-adopt-prod-agents.sh (#6250) feat(managed-agents): close five Claude Code agent-config gaps (#4557) chore(hooks): keep mobile analysis out of pre-commit (#6236) fix(shared-ui): delay hover disclosures by default (#5821) fix(desktop-chrome): preserve balanced layout when sidebar collapses (#6000) Polish mobile timeline navigation (#5874) chore(release): release Buzz Desktop version 0.5.17 (#6234) ... Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
#4557 mounted EffortPickerField into AgentInstanceEditDialog, which this PR deletes. Fold the write control into the merged dialog's instance section under the same gating (local backend + discovered effortConfigId), wiring the agent config surface query in the parent. Preserves the direct-write path, surface invalidation, and edit-agent-effort id unchanged. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…ntion-phase1 * origin/main: (71 commits) Revert "fix(acp): gate relay-signed workflow messages on their attributed author" (#6311) fix(desktop): morph the drawer panel icon instead of sliding it (#6306) feat(desktop): refine repository-aware project workspaces (#6003) Fix mobile Activity thread navigation (#5850) perf(desktop): parallelize relay agent directory rebuild (#6258) Refine the mobile emoji picker (#5853) fix(desktop): exclude archived agents from nest, order regeneration (#5905) Add font size and conversation density preferences (#5644) fix(desktop): emit camelCase config-write payload fields (#6062) fix(desktop): downscale large avatars for agent-share PNG body (#6260) fix(desktop): preserve early relay auth challenges (#3320) Polish mobile message actions (#5873) Refine mobile pairing confirmation (#6018) chore(scripts): add buzz-adopt-prod-agents.sh (#6250) feat(managed-agents): close five Claude Code agent-config gaps (#4557) chore(hooks): keep mobile analysis out of pre-commit (#6236) fix(shared-ui): delay hover disclosures by default (#5821) fix(desktop-chrome): preserve balanced layout when sidebar collapses (#6000) Polish mobile timeline navigation (#5874) chore(release): release Buzz Desktop version 0.5.17 (#6234) ... Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
block#4557 ("close five Claude Code agent-config gaps") landed on main and conflicts with this branch. Resolutions: 1. `lib.rs` deep_link import. Both sides had independently dropped the two standalone `use huddle::` lines; the only real difference was the glob. The glob stays -- it is what keeps lib.rs under the size ratchet, four other globs in the same file make it idiomatic, and it cannot go stale when main adds another deep_link symbol. 2. `lib.rs` generate_handler list. Additive on both sides: main added `persist_agent_effort_level`, this branch the two routing-policy commands. All three kept. 3. `reader.rs` config_file_path. Main extracted the logic into `config_file_path_for_runtime` and threaded a new `claude_config_dir` parameter through it, while this branch had added an OpenCode special case at the old call site. Taking main's side alone silently drops OpenCode config discovery, so the special case moved INTO main's helper -- where it now sits beside the `opencode` arm that already exists in the sibling `mcp_config_file_path_for_runtime`. Two further defects that no textual merge could surface: - `reader_tests_opencode.rs` did not compile. Main gave `read_config_surface` a fifth parameter (`claude_config_dir`); this file is new on this branch and untouched by main, so git merged it cleanly and the call kept passing four arguments. This is the third time this branch has been broken by exactly that shape -- a signature or struct change upstream in code the branch never touched. - The preset-logo guard was asserting on itself, not on the data. This branch moved `KNOWN_ACP_RUNTIMES` out of `discovery.rs` into `discovery/known_runtimes.rs` and gave it `pub(super)` visibility, but left the guard reading the old path with a regex requiring a bare `const`. Both halves stopped matching, so it failed on `could not locate KNOWN_ACP_RUNTIMES` before checking anything. This was broken on the branch tip before this merge, not caused by it. The guard now reads the real file and tolerates an optional visibility modifier, so a future move cannot re-break the match the same way. Verified in this worktree on x86_64-pc-windows-msvc: - `cargo check --manifest-path desktop/src-tauri/Cargo.toml --all-targets`: clean (was error[E0061]). - `cargo test --manifest-path desktop/src-tauri/Cargo.toml --lib`: 2450 passed, 1 failed -- `claude_spawn_uses_the_probed_cli_executable`, which passes in isolation and fails the same way on clean main. - `pnpm exec tsc --noEmit`: clean. - `pnpm check` (biome + file-size ratchet + px-text + pubkey guards): exit 0. - `presetLogos.test.mjs`: 11 passed (was a hard failure). Signed-off-by: Michael Feth <michael@jira-flow.com>
…k#4938) Add the Auto variant to PermissionMode (wire string 'auto'; block#4557 adds the same variant from the claude-config arc — this commit establishes the contradiction logic ahead of that merge so the rebase is mechanical). Auto mode = fully autonomous execution; model-gated (requires supportsAutoMode); the adapter self-approves all tool calls internally and never emits session/request_permission. Mode matrix: - allow + auto → compatible (transmit as-is; both want unattended approval) - ask + auto → startup error (card never fires — ask becomes a dead letter) - reject + auto → startup error (inverted-security worst case: policy says deny while adapter silently auto-approves everything) Tests: 4 new pinned tests (allow+auto ok, ask+auto error, reject+auto error, wire string correct). Total: 724 passing. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Closes five Claude Code agent-config gaps in Buzz Desktop, split into three commits that share the spawn-time and live-switch surfaces.
Config isolation, model authority, and Auto mode (#2692, #2884, #3493)
CLAUDE_CONFIG_DIRisolation (Agent config panel lists MCP servers from hardcoded ~/.claude.json, ignoring per-agent CLAUDE_CONFIG_DIR #3493).config_bridgeresolves bothsettings.jsonand.claude.jsonpanel paths from the agent's effective env (resolve_effective_agent_env— baked floor → definition → global → persona → record), so the panel reads the same directory the agent runs against.mcp_config_file_path_for_runtimehonors a custom dir; empty/blank is treated as unset, matching Claude'sCLAUDE_CONFIG_DIR || homedir()semantics.AgentConfigPanelshows a Keychain caveat when a custom dir is active (a custom dir maps to a fresh Keychain namespace unlessCLAUDE_SECURESTORAGE_CONFIG_DIRis also set).ANTHROPIC_MODELis the sole startup model authority for Claude. Local spawns writeANTHROPIC_MODELand stripBUZZ_ACP_MODELso the harness never sees two authorities; remote deploys sendANTHROPIC_MODELinpolicy_envinstead ofBUZZ_ACP_MODEL. Non-Claude runtimes are unchanged.PermissionMode::Auto(buzz-acp: no way to select Claude Code'sautopermission mode — adapter advertises it, PermissionMode omits it #2884). Wire string"auto", model-gated, degrades to the agent default when the active model doesn't advertise it.Thinking effort end-to-end for local Claude agents
Effort flows from the running session's discovered
thought_levelconfig option through the config surface to a local-only write control and a read-only two-facts display.thought_levelconfig option from the session cache (never hardcoded) and populateseffort_config_id/effort_optionsonRuntimeConfigSurface. The canonical effort tier orders record env >record.effort_level(BuzzExplicit) > ACP > persona > global > definition > file, so the panel shows the effort the next spawn will launch with whileresolve_with_overridesurfaces the running ACP value as the struck-through override — neither masks the other silently.persist_agent_effort_levelis a direct-write Tauri setter (writesrecord.effort_level+updated_at,save_managed_agents) that rejects non-local backends — remote effort is set at deploy time viapolicy_env.EffortPickerFieldmounts inAgentInstanceEditDialogbeside the Model block, gated on a local backend AND a discoveredeffortConfigId. It persists directly and invalidates the config surface, mirroring thesetManagedAgentAutoRestartstandalone-setter precedent, so the frozenUpdateManagedAgentInputshape stays frozen.thinkingEffortnormalized field inAgentConfigPanel, fed by the reader's canonical tier ordering.buzz-acpapplies the startup effort env at session start.Distinguish a rejected model switch from silent success
A live model switch collapsed adapter rejection into success:
apply_model_switchreturnedOkon both an accepted switch and an application-level refusal, so the caller cached pre-switch capabilities as if they described the target model and Desktop reported the pick as landed.ModelSwitchOutcome::{Applied(Value),Rejected}. Transport-class errors still propagate asErr(respawn the poisoned stdio); an application-level refusal is nowRejected.post_switch_snapshot:Appliedrefreshesmodel_capabilitiesfrom the target model's echoedconfigOptions(or drops toNonewhen none are echoed, so a pre-switch snapshot is never mistaken for the target's);Rejectedpreserves pre-switch caps and emits afailurecontrol_result. Effort resolution and the session-config capture read the post-switch snapshot so they converge on the model the session actually runs;modelOverriddenis gated onswitch_succeeded.liveSwitchOutcome.tsgains a distinct"failed"outcome for the adapterfailureframe and treats the busy-path"sent"ack as provisional — it never counts toward success. Success is confirmed only by a real positive terminal frame (the busy-path deferred apply emits a correlatedswitchedcontrol_resultwhen it lands), and the fallback timeout resolves an honest"pending"(accepted, apply deferred), never a false"ok".ModelPickersurfaces a distinct toast per outcome — failed, unsupported, and pending.Scope explicitly excluded
Per-agent config dir provisioning,
CLAUDE_SECURESTORAGE_CONFIG_DIRsentinel injection,settings.jsonprojection, protected-key stripping, MCP inheritance, spawn serialization, and thelast_spawn_warningssurface are absent from this diff. Silent-fallback machinery for non-Claude runtimes (#2265/#4004) is a tracked follow-up.Sanctioned follow-ups
BUZZ_ACP_EFFORT_LEVELonce and applies it at session creation. The live effort-switch machinery (mid-conversation effort RPC + ack frame) was deliberately removed and is archived onarchive/claude-config-gaps-live-effortfor a future plan-gated revival.switchedimmediately after catalog validation, but the realset_config_optionruns at the next session creation — potentially much later — so a rejection there is not surfaced back to the picker (holding a subscription that long is not sensible). Pre-existing, catalog-gated behavior; a durable fix is a tracked follow-up.Closes #2692, #2884, #3493